From 1aaaf85166c4947d00e47c784c48c5b9ae0cedb7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 14 Jul 2014 08:06:14 -0700 Subject: [PATCH] Add an installation script to the repo This updates the `make install` target and adds a new `make dist` target which will prepare a distributable tarball with an install script. All work is based off the equivalent rust nightly tarball and nightly installation scripts. Closes #159 Closes #184 --- Makefile | 75 ++++++-- README.md | 4 +- src/bin/cargo.rs | 15 +- src/install.sh | 472 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 542 insertions(+), 24 deletions(-) create mode 100755 src/install.sh diff --git a/Makefile b/Makefile index e10fedb1a..0bab186af 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ RUSTC_FLAGS ?= DESTDIR ?= PREFIX ?= /usr/local -BINDIR ?= $(PREFIX)/bin +TARGET ?= target +PKG_NAME ?= cargo-nightly ifeq ($(wildcard rustc/bin),) export RUSTC := rustc @@ -29,8 +30,11 @@ DEPS = -L libs/hammer.rs/target -L libs/toml-rs/build TOML = libs/toml-rs/build/$(shell $(RUSTC) --print-file-name libs/toml-rs/src/toml.rs) HAMMER = libs/hammer.rs/target/$(shell $(RUSTC) --crate-type=lib --print-file-name libs/hammer.rs/src/hammer.rs) HAMCREST = libs/hamcrest-rust/target/libhamcrest.timestamp -LIBCARGO = target/libcargo.timestamp -BIN_TARGETS = $(patsubst %,target/%,$(BINS)) +LIBCARGO = $(TARGET)/libcargo.rlib +TESTDIR = $(TARGET)/tests +DISTDIR = $(TARGET)/dist +PKGDIR = $(DISTDIR)/$(PKG_NAME) +BIN_TARGETS = $(BINS:%=$(TARGET)/%) all: $(BIN_TARGETS) @@ -45,36 +49,39 @@ $(TOML): $(wildcard libs/toml-rs/src/*.rs) $(HAMCREST): $(shell find libs/hamcrest-rust/src/hamcrest -name '*.rs') $(MAKE) -C libs/hamcrest-rust +$(TARGET)/: + mkdir -p $@ + +$(TESTDIR)/: + mkdir -p $@ + # === Cargo -$(LIBCARGO): $(SRC) $(HAMMER) $(TOML) - mkdir -p target - $(RUSTC) $(RUSTC_FLAGS) $(DEPS) --out-dir target src/cargo/lib.rs - touch $(LIBCARGO) +$(LIBCARGO): $(SRC) $(HAMMER) $(TOML) | $(TARGET)/ + $(RUSTC) $(RUSTC_FLAGS) $(DEPS) --out-dir $(TARGET) src/cargo/lib.rs libcargo: $(LIBCARGO) # === Commands -$(BIN_TARGETS): target/%: src/bin/%.rs $(HAMMER) $(TOML) $(LIBCARGO) - $(RUSTC) $(RUSTC_FLAGS) $(DEPS) -Ltarget --out-dir target $< +$(BIN_TARGETS): $(TARGET)/%: src/bin/%.rs $(HAMMER) $(TOML) $(LIBCARGO) + $(RUSTC) $(RUSTC_FLAGS) $(DEPS) -L$(TARGET) --out-dir $(TARGET) $< # === Tests TEST_SRC = $(shell find tests -name '*.rs') TEST_DEPS = $(DEPS) -L libs/hamcrest-rust/target -target/tests/test-integration: $(HAMCREST) $(TEST_SRC) $(BIN_TARGETS) - $(RUSTC) --test --crate-type=lib $(TEST_DEPS) -Ltarget -o $@ tests/tests.rs +$(TESTDIR)/test-integration: $(HAMCREST) $(TEST_SRC) $(BIN_TARGETS) | $(TESTDIR)/ + $(RUSTC) --test $(TEST_DEPS) -L$(TARGET) -o $@ tests/tests.rs -target/tests/test-unit: $(TOML) $(HAMCREST) $(SRC) $(HAMMER) - mkdir -p target/tests +$(TESTDIR)/test-unit: $(TOML) $(HAMCREST) $(SRC) $(HAMMER) | $(TESTDIR)/ $(RUSTC) --test -g $(RUSTC_FLAGS) $(TEST_DEPS) -o $@ src/cargo/lib.rs -test-unit: target/tests/test-unit - target/tests/test-unit $(only) +test-unit: $(TESTDIR)/test-unit + $< $(only) -test-integration: target/tests/test-integration +test-integration: $(TESTDIR)/test-integration $< $(only) test: test-unit test-integration style no-exes @@ -88,14 +95,42 @@ no-exes: && exit 1 || exit 0 clean: - rm -rf target + rm -rf $(TARGET) clean-all: clean git submodule foreach make clean -install: - install -d $(DESTDIR)$(BINDIR) - install target/cargo target/cargo-* $(DESTDIR)$(BINDIR) +dist: $(DISTDIR)/$(PKG_NAME).tar.gz + +distcheck: dist + rm -rf $(TARGET)/distcheck + mkdir -p $(TARGET)/distcheck + (cd $(TARGET)/distcheck && tar xf ../dist/$(PKG_NAME).tar.gz) + $(TARGET)/distcheck/$(PKG_NAME)/install.sh \ + --prefix=$(TARGET)/distcheck/install + $(TARGET)/distcheck/install/bin/cargo -h > /dev/null + $(TARGET)/distcheck/$(PKG_NAME)/install.sh \ + --prefix=$(TARGET)/distcheck/install --uninstall + [ -f $(TARGET)/distcheck/install/bin/cargo ] && exit 1 || exit 0 + +$(DISTDIR)/$(PKG_NAME).tar.gz: $(PKGDIR)/lib/cargo/manifest.in + tar -czvf $@ -C $(DISTDIR) $(PKG_NAME) + +$(PKGDIR)/lib/cargo/manifest.in: $(BIN_TARGETS) Makefile + rm -rf $(PKGDIR) + mkdir -p $(PKGDIR)/bin $(PKGDIR)/lib/cargo + cp $(TARGET)/cargo $(PKGDIR)/bin + cp $(BIN_TARGETS) $(PKGDIR)/lib/cargo + rm $(PKGDIR)/lib/cargo/cargo + (cd $(PKGDIR) && find . -type f | sed 's/^\.\///') \ + > $(DISTDIR)/manifest-$(PKG_NAME).in + cp src/install.sh $(PKGDIR) + cp README.md LICENSE-MIT LICENSE-APACHE $(PKGDIR) + cp LICENSE-MIT $(PKGDIR) + mv $(DISTDIR)/manifest-$(PKG_NAME).in $(PKGDIR)/lib/cargo/manifest.in + +install: $(PKGDIR)/lib/cargo/manifest.in + $(PKGDIR)/install.sh --prefix=$(PREFIX) # Setup phony tasks .PHONY: all clean distclean test test-unit test-integration libcargo style diff --git a/README.md b/README.md index cef99a6d4..fe097f865 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Cargo downloads your Rust project’s dependencies and compiles your project. Learn more at http://crates.io/. -## Installing cargo +## Installing cargo from nightlies Cargo has nightlies available for use. The cargo source is not always guaranteed to compile on rust master as it may lag behind by a day or two. Nightlies, @@ -11,7 +11,7 @@ however, will run regardless of this fact! ``` $ curl -O http://static.rust-lang.org/cargo-dist/cargo-nightly-linux.tar.gz $ tar xf cargo-nightly-linux.tar.gz -$ ./cargo-nightly/bin/cargo build +$ ./cargo-nightly/install.sh ``` The current nightlies available are: diff --git a/src/bin/cargo.rs b/src/bin/cargo.rs index 8379a85eb..b21b416aa 100644 --- a/src/bin/cargo.rs +++ b/src/bin/cargo.rs @@ -58,9 +58,20 @@ fn execute() { println!("Options (for all commands):\n\n{}", options); }, _ => { - let command = format!("cargo-{}", cmd); + let command = format!("cargo-{}{}", cmd, os::consts::EXE_SUFFIX); let mut command = match os::self_exe_path() { - Some(path) => Command::new(path.join(command)), + Some(path) => { + let p1 = path.join("../lib/cargo").join(command.as_slice()); + let p2 = path.join(command.as_slice()); + println!("{} {}", p1.display(), p2.display()); + if p1.exists() { + Command::new(p1) + } else if p2.exists() { + Command::new(p2) + } else { + Command::new(command) + } + } None => Command::new(command), }; let command = command diff --git a/src/install.sh b/src/install.sh new file mode 100755 index 000000000..79e7a3210 --- /dev/null +++ b/src/install.sh @@ -0,0 +1,472 @@ +#!/bin/sh +# Copyright 2014 The Rust Project Developers. See the COPYRIGHT +# file at the top-level directory of this distribution and at +# http://rust-lang.org/COPYRIGHT. +# +# Licensed under the Apache License, Version 2.0 or the MIT license +# , at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +msg() { + echo "install: $1" +} + +step_msg() { + msg + msg "$1" + msg +} + +warn() { + echo "install: WARNING: $1" +} + +err() { + echo "install: error: $1" + exit 1 +} + +need_ok() { + if [ $? -ne 0 ] + then + err "$1" + fi +} + +need_cmd() { + if command -v $1 >/dev/null 2>&1 + then msg "found $1" + else err "need $1" + fi +} + +putvar() { + local T + eval T=\$$1 + eval TLEN=\${#$1} + if [ $TLEN -gt 35 ] + then + printf "install: %-20s := %.35s ...\n" $1 "$T" + else + printf "install: %-20s := %s %s\n" $1 "$T" "$2" + fi +} + +valopt() { + VAL_OPTIONS="$VAL_OPTIONS $1" + + local OP=$1 + local DEFAULT=$2 + shift + shift + local DOC="$*" + if [ $HELP -eq 0 ] + then + local UOP=$(echo $OP | tr '[:lower:]' '[:upper:]' | tr '\-' '\_') + local V="CFG_${UOP}" + eval $V="$DEFAULT" + for arg in $CFG_ARGS + do + if echo "$arg" | grep -q -- "--$OP=" + then + val=$(echo "$arg" | cut -f2 -d=) + eval $V=$val + fi + done + putvar $V + else + if [ -z "$DEFAULT" ] + then + DEFAULT="" + fi + OP="${OP}=[${DEFAULT}]" + printf " --%-30s %s\n" "$OP" "$DOC" + fi +} + +opt() { + BOOL_OPTIONS="$BOOL_OPTIONS $1" + + local OP=$1 + local DEFAULT=$2 + shift + shift + local DOC="$*" + local FLAG="" + + if [ $DEFAULT -eq 0 ] + then + FLAG="enable" + else + FLAG="disable" + DOC="don't $DOC" + fi + + if [ $HELP -eq 0 ] + then + for arg in $CFG_ARGS + do + if [ "$arg" = "--${FLAG}-${OP}" ] + then + OP=$(echo $OP | tr 'a-z-' 'A-Z_') + FLAG=$(echo $FLAG | tr 'a-z' 'A-Z') + local V="CFG_${FLAG}_${OP}" + eval $V=1 + putvar $V + fi + done + else + if [ ! -z "$META" ] + then + OP="$OP=<$META>" + fi + printf " --%-30s %s\n" "$FLAG-$OP" "$DOC" + fi +} + +flag() { + BOOL_OPTIONS="$BOOL_OPTIONS $1" + + local OP=$1 + shift + local DOC="$*" + + if [ $HELP -eq 0 ] + then + for arg in $CFG_ARGS + do + if [ "$arg" = "--${OP}" ] + then + OP=$(echo $OP | tr 'a-z-' 'A-Z_') + local V="CFG_${OP}" + eval $V=1 + putvar $V + fi + done + else + if [ ! -z "$META" ] + then + OP="$OP=<$META>" + fi + printf " --%-30s %s\n" "$OP" "$DOC" + fi +} + +validate_opt () { + for arg in $CFG_ARGS + do + isArgValid=0 + for option in $BOOL_OPTIONS + do + if test --disable-$option = $arg + then + isArgValid=1 + fi + if test --enable-$option = $arg + then + isArgValid=1 + fi + if test --$option = $arg + then + isArgValid=1 + fi + done + for option in $VAL_OPTIONS + do + if echo "$arg" | grep -q -- "--$option=" + then + isArgValid=1 + fi + done + if [ "$arg" = "--help" ] + then + echo + echo "No more help available for Configure options," + echo "check the Wiki or join our IRC channel" + break + else + if test $isArgValid -eq 0 + then + err "Option '$arg' is not recognized" + fi + fi + done +} + +absolutify() { + FILE_PATH="${1}" + FILE_PATH_DIRNAME="$(dirname ${FILE_PATH})" + FILE_PATH_BASENAME="$(basename ${FILE_PATH})" + FILE_ABS_PATH="$(cd ${FILE_PATH_DIRNAME} && pwd)" + FILE_PATH="${FILE_ABS_PATH}/${FILE_PATH_BASENAME}" + # This is the return value + ABSOLUTIFIED="${FILE_PATH}" +} + +msg "looking for install programs" +need_cmd mkdir +need_cmd printf +need_cmd cut +need_cmd grep +need_cmd uname +need_cmd tr +need_cmd sed + +CFG_SRC_DIR="$(cd $(dirname $0) && pwd)" +CFG_SELF="$0" +CFG_ARGS="$@" + +HELP=0 +if [ "$1" = "--help" ] +then + HELP=1 + shift + echo + echo "Usage: $CFG_SELF [options]" + echo + echo "Options:" + echo +else + step_msg "processing $CFG_SELF args" +fi + +# Check for mingw or cygwin in order to special case $CFG_LIBDIR_RELATIVE. +# This logic is duplicated from configure in order to get the correct libdir +# for Windows installs. +CFG_OSTYPE=$(uname -s) + +case $CFG_OSTYPE in + + MINGW32*) + CFG_OSTYPE=pc-mingw32 + ;; + + MINGW64*) + # msys2, MSYSTEM=MINGW64 + CFG_OSTYPE=w64-mingw32 + ;; + +# Thad's Cygwin identifers below + +# Vista 32 bit + CYGWIN_NT-6.0) + CFG_OSTYPE=pc-mingw32 + ;; + +# Vista 64 bit + CYGWIN_NT-6.0-WOW64) + CFG_OSTYPE=w64-mingw32 + ;; + +# Win 7 32 bit + CYGWIN_NT-6.1) + CFG_OSTYPE=pc-mingw32 + ;; + +# Win 7 64 bit + CYGWIN_NT-6.1-WOW64) + CFG_OSTYPE=w64-mingw32 + ;; +esac + +OPTIONS="" +BOOL_OPTIONS="" +VAL_OPTIONS="" + +# On windows we just store the libraries in the bin directory because +# there's no rpath. This is where the build system itself puts libraries; +# --libdir is used to configure the installation directory. +# FIXME: Thise needs to parameterized over target triples. Do it in platform.mk +CFG_LIBDIR_RELATIVE=lib +if [ "$CFG_OSTYPE" = "pc-mingw32" ] || [ "$CFG_OSTYPE" = "w64-mingw32" ] +then + CFG_LIBDIR_RELATIVE=bin +fi + +flag uninstall "only uninstall from the installation prefix" +opt verify 1 "verify that the installed binaries run correctly" +valopt prefix "/usr/local" "set installation prefix" +# NB This isn't quite the same definition as in `configure`. +# just using 'lib' instead of CFG_LIBDIR_RELATIVE +valopt libdir "${CFG_PREFIX}/${CFG_LIBDIR_RELATIVE}" "install libraries" +valopt mandir "${CFG_PREFIX}/share/man" "install man pages in PATH" + +if [ $HELP -eq 1 ] +then + echo + exit 0 +fi + +step_msg "validating $CFG_SELF args" +validate_opt + +# OK, let's get installing ... + +# Sanity check: can we run the binaries? +if [ -z "${CFG_DISABLE_VERIFY}" ] +then + # Don't do this if uninstalling. Failure here won't help in any way. + if [ -z "${CFG_UNINSTALL}" ] + then + msg "verifying platform can run binaries" + "${CFG_SRC_DIR}/bin/cargo" -h > /dev/null + if [ $? -ne 0 ] + then + err "can't execute rustc binary on this platform" + fi + fi +fi + +# Sanity check: can we can write to the destination? +msg "verifying destination is writable" +umask 022 && mkdir -p "${CFG_LIBDIR}" +need_ok "can't write to destination. consider \`sudo\`." +touch "${CFG_LIBDIR}/rust-install-probe" > /dev/null +if [ $? -ne 0 ] +then + err "can't write to destination. consider \`sudo\`." +fi +rm -f "${CFG_LIBDIR}/rust-install-probe" +need_ok "failed to remove install probe" + +# Sanity check: don't install to the directory containing the installer. +# That would surely cause chaos. +msg "verifying destination is not the same as source" +INSTALLER_DIR="$(cd $(dirname $0) && pwd)" +PREFIX_DIR="$(cd ${CFG_PREFIX} && pwd)" +if [ "${INSTALLER_DIR}" = "${PREFIX_DIR}" ] +then + err "can't install to same directory as installer" +fi + +# Using an absolute path to libdir in a few places so that the status +# messages are consistently using absolute paths. +absolutify "${CFG_LIBDIR}" +ABS_LIBDIR="${ABSOLUTIFIED}" + +# The file name of the manifest we're going to create during install +INSTALLED_MANIFEST="${ABS_LIBDIR}/cargo/manifest" + +# First, uninstall from the installation prefix. +# Errors are warnings - try to rm everything in the manifest even if some fail. +if [ -f "${INSTALLED_MANIFEST}" ] +then + # Iterate through installed manifest and remove files + while read p; do + # The installed manifest contains absolute paths + msg "removing $p" + if [ -f "$p" ] + then + rm -f "$p" + if [ $? -ne 0 ] + then + warn "failed to remove $p" + fi + else + warn "supposedly installed file $p does not exist!" + fi + done < "${INSTALLED_MANIFEST}" + + # If we fail to remove cargo below, then the installed manifest will + # still be full; the installed manifest needs to be empty before install. + msg "removing ${INSTALLED_MANIFEST}" + rm -f "${INSTALLED_MANIFEST}" + # For the above reason, this is a hard error + need_ok "failed to remove installed manifest" + + # Remove 'rustlib' directory + msg "removing ${ABS_LIBDIR}/cargo" + rm -Rf "${ABS_LIBDIR}/cargo" + if [ $? -ne 0 ] + then + warn "failed to remove cargo" + fi +else + # There's no manifest. If we were asked to uninstall, then that's a problem. + if [ -n "${CFG_UNINSTALL}" ] + then + err "unable to find installation manifest at ${CFG_LIBDIR}/cargo" + fi +fi + +# If we're only uninstalling then exit +if [ -n "${CFG_UNINSTALL}" ] +then + echo + echo " Cargo is uninstalled." + echo + exit 0 +fi + +# Create the installed manifest, which we will fill in with absolute file paths +mkdir -p "${CFG_LIBDIR}/cargo" +need_ok "failed to create cargo" +touch "${INSTALLED_MANIFEST}" +need_ok "failed to create installed manifest" + +# Now install, iterate through the new manifest and copy files +while read p; do + + # Decide the destination of the file + FILE_INSTALL_PATH="${CFG_PREFIX}/$p" + + if echo "$p" | grep "^lib/" > /dev/null + then + pp=`echo $p | sed 's/^lib\///'` + FILE_INSTALL_PATH="${CFG_LIBDIR}/$pp" + fi + + if echo "$p" | grep "^share/man/" > /dev/null + then + pp=`echo $p | sed 's/^share\/man\///'` + FILE_INSTALL_PATH="${CFG_MANDIR}/$pp" + fi + + # Make sure there's a directory for it + umask 022 && mkdir -p "$(dirname ${FILE_INSTALL_PATH})" + need_ok "directory creation failed" + + # Make the path absolute so we can uninstall it later without + # starting from the installation cwd + absolutify "${FILE_INSTALL_PATH}" + FILE_INSTALL_PATH="${ABSOLUTIFIED}" + + # Install the file + msg "${FILE_INSTALL_PATH}" + if echo "$p" | grep "^bin/\\|/cargo-" > /dev/null + then + install -m755 "${CFG_SRC_DIR}/$p" "${FILE_INSTALL_PATH}" + else + install -m644 "${CFG_SRC_DIR}/$p" "${FILE_INSTALL_PATH}" + fi + need_ok "file creation failed" + + # Update the manifest + echo "${FILE_INSTALL_PATH}" >> "${INSTALLED_MANIFEST}" + need_ok "failed to update manifest" + +# The manifest lists all files to install +done < "${CFG_SRC_DIR}/${CFG_LIBDIR_RELATIVE}/cargo/manifest.in" + +# Sanity check: can we run the installed binaries? +if [ -z "${CFG_DISABLE_VERIFY}" ] +then + msg "verifying installed binaries are executable" + "${CFG_PREFIX}/bin/cargo" -h > /dev/null + if [ $? -ne 0 ] + then + ERR="can't execute installed rustc binary. " + ERR="${ERR}installation may be broken. " + ERR="${ERR}if this is expected then rerun install.sh with \`--disable-verify\` " + ERR="${ERR}or \`make install\` with \`--disable-verify-install\`" + err "${ERR}" + fi +fi + +echo +echo " Cargo is ready to roll." +echo + + -- 2.30.2